package com.jintian.smart.kernel.common.expr;

import com.jintian.smart.kernel.common.util.StringUtil;
import com.jintian.smart.kernel.core.ExprFunction;
import com.jintian.smart.kernel.core.annotation.expr.ExprFn;
import com.jintian.smart.kernel.core.expr.IFunctionFilter;
import com.jintian.smart.kernel.core.expr.IFunctionRepository;
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 加载exprFn中指定的函数
 */
@Slf4j
public class DefaultFunctionRepository implements IFunctionRepository {

	private IFunctionFilter IFunctionFilter;

	private ClassLoader functionClassLoader;

	private Map<String, ExprFunction> functions = new HashMap<>();

	public DefaultFunctionRepository() {
		try {
			this.load();
		} catch (IOException e) {
		}
	}

	@Override
	public Collection<ExprFunction> getFunctions() {
		return Collections.unmodifiableCollection(functions.values());
	}

	@Override
	public Map<String, Method> getNamedMethod() {
		return Collections.unmodifiableMap(
				functions.values().stream().collect(Collectors.toMap(e -> e.getName(), e -> e.getMethod())));
	}

	@Override
	public Set<String> registerStaticMethod(Class<?> clazz) {
		Set<String> ret = new HashSet<>();
		Collection<ExprFunction> fnList = this.parseExprFunction(clazz);
		for (ExprFunction fn : fnList) {
			ret.add(fn.getName());
			this.functions.put(fn.getName(), fn);
		}
		return ret;
	}

	@Override
	public Set<String> registerMethod(String fnName, Method method, Object target) {
		if (!Modifier.isPublic(method.getModifiers()) || !Modifier.isStatic(method.getModifiers())) {
			return Collections.emptySet();
		}
		Set<String> ret = new HashSet<>();
		ExprFn anno = method.getAnnotation(ExprFn.class);
		if (anno != null && anno.ignore()) {
			return Collections.emptySet();
		}
		if (fnName != null) {
			ret.add(fnName);
			this.functions.put(fnName, ExprFunction.builder().name(fnName).method(method).target(target).build());
		} else {
			List<String> fnNames = getExprFnName(method);
			for (String name : fnNames) {
				ret.add(name);
				this.functions.put(name, ExprFunction.builder().name(name).method(method).target(target).build());
			}
		}
		return ret;
	}

	protected void load() throws IOException {
		final ClassLoader contextCL = functionClassLoader == null ? Thread.currentThread().getContextClassLoader()
				: functionClassLoader;
		final ClassLoader classLoader = contextCL == null ? this.getClass().getClassLoader() : contextCL;
		Enumeration<URL> ee = classLoader.getResources("exprFn");
		Set<String> staticFnSet = new HashSet<>();
		while (ee.hasMoreElements()) {
			URL url = ee.nextElement();
			try (InputStream stream = url.openStream()) {
				if (stream != null) {
					BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
					String line = null;
					while ((line = reader.readLine()) != null) {
						if (this.isEmpty(line))
							continue;
						try {
							int idx = line.indexOf("=");
							if (idx > 0) {
								String fnName = line.substring(0, idx);
								String fnDesc = line.substring(idx + 1);
								String className = fnDesc.substring(0, fnDesc.lastIndexOf("."));
								Class<?> clazz = classLoader.loadClass(className);
								String methodParams = fnDesc.substring(fnDesc.lastIndexOf(".") + 1);
								if (methodParams.contains("(") && methodParams.contains(")")) {
									String methodName = methodParams.substring(0, methodParams.indexOf("("));
									String[] params = methodParams
											.substring(methodParams.indexOf("(") + 1, methodParams.indexOf(")"))
											.split(",");
									Class<?>[] parameterTypes = new Class[params.length];
									for (int i = 0; i < params.length; i++) {
										parameterTypes[i] = classLoader.loadClass(params[i]);
									}
									Method method = clazz.getMethod(methodName, parameterTypes);
									staticFnSet.add(fnName);
									this.functions.put(fnName,
											ExprFunction.builder().method(method).name(fnName).target(clazz).build());
								} else {

								}
							} else {
								Class<?> clazz = classLoader.loadClass(line);
								staticFnSet.addAll(this.registerStaticMethod(clazz));
							}
						} catch (Exception ex) {
							log.error("{}解析表达式函数失败", line, ex);
						}

					}
				}
			} catch (IOException e) {
				log.error("读取表达式函数配置文件失败,{}", e.getMessage());
			}
		}
		if (staticFnSet.size() > 0) {
			StringBuffer sb = new StringBuffer(
					"默认java计算表达式函数:\n----------------------------------------------------------\n")
							.append(StringUtil.join(staticFnSet.toArray(new String[0]), ","));
			log.info("{}\n----------------------------------------------------------", sb.toString());
		}
	}

	private Collection<ExprFunction> parseExprFunction(Class<?> clazz) {
		Collection<ExprFunction> ret = new ArrayList<>();
		Method[] methods = clazz.getMethods();
		for (Method method : methods) {
			if (!Modifier.isPublic(method.getModifiers()) || !Modifier.isStatic(method.getModifiers())) {
				continue;
			}
			if (IFunctionFilter != null && !IFunctionFilter.accept(method)) {
				continue;
			}
			List<String> fnNames = getExprFnName(method);
			for (String fnName : fnNames) {
				ret.add(ExprFunction.builder().method(method).name(fnName).target(clazz).build());
			}
		}
		return ret;
	}

	private boolean isEmpty(String str) {
		return str == null || str.trim().length() == 0;
	}

}
